/**
 * hw.c
 *
 *  Created on: 2018-05-11
 *      Author: chenshisheng
 */

#include "hw.h"
#include "gpio.h"
#include "shell.h"
#include "mlist.h"
#include "message_queue.h"

#include <stdlib.h>
#include <string.h>
#include <ctype.h>

static const struct
{
    void *port;
    uint32_t bit;
    HW_Level_t activeLevel;
    const char *name;
} _ins[] =
{
        {PC, BIT12, HW_Level_Low, "DIAG"}, // HW_In_DIAG
};

static const struct
{
    void *port;
    uint32_t bit;
    HW_Level_t activeLevel;
    HW_State_t defaultStatus;
    const char *name;
}_outs[] =
{
        {PC, BIT9,  HW_Level_Low,  HW_State_Active, "run"},  // HW_Out_Run
        {PC, BIT14, HW_Level_High, HW_State_Active, "DIR"},  // HW_Out_DIR
        {PC, BIT13, HW_Level_High, HW_State_Active, "ENN"},  // HW_Out_ENN
};

enum _Px
{
    _PA,
    _PB,
    _PC,
    _PD,
    _PE,
    _PF,
};

LIST(_paExtis);
LIST(_pbExtis);
LIST(_pcExtis);
LIST(_pdExtis);
LIST(_peExtis);
LIST(_pfExtis);

static const struct
{
    const char *name;
    void *port;
    IRQn_Type IRQn;
    list_t *pList;
}_portAttrs[] =
{
        {"PA", PA, GPA_IRQn, &_paExtis}, // _PA
        {"PB", PB, GPB_IRQn, &_pbExtis}, // _PB
        {"PC", PC, GPC_IRQn, &_pcExtis}, // _PC
        {"PD", PD, GPD_IRQn, &_pdExtis}, // _PD
        {"PE", PE, GPE_IRQn, &_peExtis}, // _PE
        {"PF", PF, GPF_IRQn, &_pfExtis}, // _PF
};

static const struct _Exti
{
    enum _Px px;
    uint32_t bit;
    unsigned int pin;
    HW_Level_t activeLevel;
    uint32_t mode;
    bool defaultEnable;
    const char *name;
}_extis[] =
{
        {_PC, BIT12, 12, HW_Level_Low, GPIO_INT_RISING, true, "DIAG"}, // HW_Exti_DIAG
};

struct _ExtiInfo
{
    struct _ExtiInfo *next; // for list
    void *port;
    uint32_t bit;
    Callback_t cb;
    void *arg;
    unsigned int flags;
}_extiInfos[ARRAY_LEN(_extis)];

static int _DefaultExitCallback(void *arg);

void HW_Init(void)
{
    unsigned int i;
    GPIO_T *port;

    GPIO_SET_DEBOUNCE_TIME(GPIO_DBCTL_DBCLKSRC_LIRC, GPIO_DBCTL_DBCLKSEL_128);
    for(i = 0; i < ARRAY_LEN(_ins); i++)
    {
        port = (GPIO_T *)_ins[i].port;
//        GPIO_SetPullCtl(port, _ins[i].bit, GPIO_PUSEL_PULL_UP);
        GPIO_SetMode(port, _ins[i].bit, GPIO_MODE_INPUT);
        GPIO_ENABLE_DEBOUNCE(port, _ins[i].bit);
    }

    for(i = 0; i < ARRAY_LEN(_outs); i++)
    {
        HW_Config(i, _outs[i].defaultStatus);
        GPIO_SetMode(_outs[i].port, _outs[i].bit, GPIO_MODE_OUTPUT);
    }

#if 0
#define _SCL A, 0

    HW_t scl = HW_STRUCT(_SCL);

    HW_PIN_INIT(&scl, HW_MODE_OUTPUT);
    HW_SET(&scl);
#endif

    for(i = 0; i < ARRAY_LEN(_portAttrs); i++)
    {
        list_init(*(_portAttrs[i].pList));
    }

    for(i = 0; i < ARRAY_LEN(_extis); i++)
    {
        const struct _Exti *exti = &_extis[i];
        enum _Px px = exti->px;
        port = _portAttrs[px].port;
        _extiInfos[i].port = port;
        _extiInfos[i].bit = exti->bit;

        GPIO_SetMode(port, exti->bit, GPIO_MODE_INPUT);
        HW_SetExtiCallback((HW_Exti_t)i, _DefaultExitCallback, (void *)i, 0);
        if(exti->defaultEnable)
        {
            GPIO_EnableInt(port, exti->pin, exti->mode);
        }

        Main_SetPriority(_portAttrs[px].IRQn, 0, 0);
        NVIC_EnableIRQ(_portAttrs[px].IRQn);
    }
}

static int _DefaultExitCallback(void *arg)
{
    HW_Exti_t exti = (HW_Exti_t)arg;
    enum _Px px = _extis[exti].px;
    struct _ExtiInfo *info = &_extiInfos[exti];

    // 设置了HW_EXTI_FLAG_REALTIME标志的外部中断会在中断处理函数内执行回调
    if(info->flags & HW_EXTI_FLAG_REALTIME)
    {
        mprintf("Exit: %s%u: %s\n", _portAttrs[px].name, _extis[exti].pin, _extis[exti].name);
    }
    else
    {
        printf("Exit: %s%u: %s\n", _portAttrs[px].name, _extis[exti].pin, _extis[exti].name);
    }

    return 0;
}

void HW_Idle(void)
{
}

void HW_Resume(void)
{
//    HW_ExtiCmd(HW_Exti_Limit, true);
}

void HW_Set(HW_Out_t out, HW_Level_t level)
{
    GPIO_T *gpio = _outs[out].port;

    if (level != HW_Level_Low)
    {
        gpio->DOUT |= _outs[out].bit;
    }
    else
    {
        gpio->DOUT &= ~ _outs[out].bit;
    }
}

HW_Level_t HW_GetSetted(HW_Out_t out)
{
    GPIO_T *gpio = _outs[out].port;

    return (gpio->DOUT & _outs[out].bit) ? HW_Level_High : HW_Level_Low;
}

HW_Level_t HW_Get(HW_In_t in)
{
    GPIO_T *gpio = _ins[in].port;

    return (gpio->PIN & _ins[in].bit) ? HW_Level_High : HW_Level_Low;
}

void HW_Toggle(HW_Out_t out)
{
    GPIO_T *gpio = _outs[out].port;

    gpio->DOUT ^= _outs[out].bit;
}

void HW_Config(HW_Out_t out, HW_State_t state)
{
    HW_Level_t level;

    level = state ? _outs[out].activeLevel : ! _outs[out].activeLevel;
    HW_Set(out, level);
}

bool HW_IsConfigedActive(HW_Out_t out)
{
    HW_Level_t level;

    level = HW_GetSetted(out);
    return level == _outs[out].activeLevel;
}

bool HW_IsActive(HW_In_t in)
{
    HW_Level_t level;

    level = HW_Get(in);
    return level == _ins[in].activeLevel;
}

/**
 *
 * @param exti
 * @param cb
 * @param arg
 */
void HW_SetExtiCallback(HW_Exti_t pin, Callback_t cb, void *arg, unsigned int flags)
{
    struct _ExtiInfo *info = &_extiInfos[pin];
    enum _Px px = _extis[pin].px;
    list_t list = *(_portAttrs[px].pList);

    info->cb = cb;
    info->arg = arg;
    info->flags = flags;
    if(flags & HW_EXTI_FLAG_REALTIME)
    {
        GPIO_DISABLE_DEBOUNCE((GPIO_T *)(info->port), info->bit);
    }
    else
    {
        GPIO_ENABLE_DEBOUNCE((GPIO_T *)(info->port), info->bit);
    }

    list_add(list, info);
}

void HW_ExtiCmd(HW_Exti_t exti, bool enable)
{
    struct _ExtiInfo *info = &_extiInfos[exti];
    GPIO_T *port = info->port;
    unsigned int pin = _extis[exti].pin;

    if(enable)
    {
        uint32_t mode = _extis[exti].mode;

        GPIO_CLR_INT_FLAG(port, info->bit);
        GPIO_EnableInt(port, pin, mode);
    }
    else
    {
        GPIO_DisableInt(port, pin);
    }
}

/**
 *
 * @param exti
 * @param mode
 * TODO 使用寄存器进行设置
 */
void HW_SetExtiMode(HW_Exti_t exti, Hw_ExtiMode_t mode)
{
    GPIO_T *port;
    const uint32_t intModes[Hw_ExtiMode_Max] =
    {
        GPIO_INT_RISING,
        GPIO_INT_FALLING,
        GPIO_INT_BOTH_EDGE,
        GPIO_INT_HIGH,
        GPIO_INT_LOW,
    };

    enum _Px px = _extis[exti].px;
    port = _portAttrs[px].port;

    GPIO_EnableInt(port, _extis[exti].pin, intModes[mode]);
}

int HW_Pin_Init(const HW_t *hw, HW_Mode_t mode, HW_Pull_t pull)
{
    uint32_t gmode;

    switch(mode)
    {
    case HW_Mode_OutputPushPull:
        gmode = GPIO_MODE_OUTPUT;
        break;

    case HW_Mode_OutputOpenDrain:
        gmode = GPIO_MODE_OPEN_DRAIN;
        break;

    case HW_Mode_Input:
        gmode = GPIO_MODE_INPUT;
        break;

    case HW_Mode_Quasi:
        gmode = GPIO_MODE_QUASI;
        break;

    default:
        return -1;
    }

    GPIO_SetMode(hw->port, hw->bit, gmode);
    if(mode == HW_Mode_OutputPushPull)
    {
        return 0;
    }

#if defined(__M451SERIES_H__)
    UNUSED(pull);
#else
    switch(pull)
    {
    case HW_Pull_Disable:
        gpull = GPIO_PUSEL_DISABLE;
        break;

    case HW_Pull_Up:
        gpull = GPIO_PUSEL_PULL_UP;
        break;

    case HW_Pull_Down:
        gpull = GPIO_PUSEL_PULL_DOWN;
        break;

    default:
        return -1;
    }

    GPIO_SetPullCtl(hw->port, hw->bit, gpull);
#endif
    return 0;
}

void HW_SetSchmitInput(const HW_t *hw, bool isEnable)
{
    if(isEnable)
    {
        BIT_SET(hw->port->SMTEN, hw->bit);
    }
    else
    {
        BIT_CLR(hw->port->SMTEN, hw->bit);
    }
}

void HW_SetHighSlewRate(const HW_t *hw, bool isEnable)
{
    if(isEnable)
    {
        BIT_SET(hw->port->SLEWCTL, hw->bit);
    }
    else
    {
        BIT_CLR(hw->port->SLEWCTL, hw->bit);
    }
}

static inline void _IRQHandler(enum _Px px)
{
    GPIO_T *port = _portAttrs[px].port;
    list_t list = *(_portAttrs[px].pList);
    struct _ExtiInfo *info;
    bool haveExti = false;

    for(info = list_head(list); info != NULL; info = list_item_next(info))
    {
        if(GPIO_GET_INT_FLAG(port, info->bit))
        {
            GPIO_CLR_INT_FLAG(port, info->bit);
            haveExti = true;
            if(info->flags & HW_EXTI_FLAG_REALTIME)
            {
                info->cb(info->arg);
            }
            else
            {
                MsgQueue_CallLater(info->cb, info->arg);
            }
        }
    }

    if(! haveExti)
    {
        /* Un-expected interrupt. Just clear all PC interrupts */
        uint32_t src = port->INTSRC;

        port->INTSRC = src;
//        __DEBUG_BKPT();
        mdebug("%s: Un-expected exti interrupts.\n", _portAttrs[px].name);
    }
}

void GPA_IRQHandler(void)
{
    _IRQHandler(_PA);
}

void GPB_IRQHandler(void)
{
    _IRQHandler(_PB);
}

void GPC_IRQHandler(void)
{
    _IRQHandler(_PC);
}

void GPD_IRQHandler(void)
{
    _IRQHandler(_PD);
}

void GPE_IRQHandler(void)
{
    _IRQHandler(_PE);
}

void GPF_IRQHandler(void)
{
    _IRQHandler(_PF);
}

static int _ShellCmdIoIn(void *stream, int argc, char *argv[])
{
    HW_In_t in;
    char name[8];
    unsigned int len, i;
    bool isExist;

    if(argc == 1)
    {
        for(in = 0; in < ARRAY_LEN(_ins); in++)
        {
            chprintf(stream, "%s: %s\n", _ins[in].name, HW_Get(in) == HW_Level_High ? "1" : "0");
        }
    }
    else if(argc == 2)
    {
        len = strlen(argv[1]);
        if(len >= ARRAY_LEN(name))
        {
            chprintf(stream, "Unknown IO name!\n");
            return -1;
        }

        for(i = 0; i < len; i++)
        {
            name[i] = toupper(argv[1][i]);
        }
        name[i] = '\0';

        isExist = FALSE;
        for(in = 0; in < ARRAY_LEN(_ins); in++)
        {
            if(strcmp(name, _ins[in].name) == 0)
            {
                chprintf(stream, "%s: %s\n", _ins[in].name, HW_Get(in) == HW_Level_High ? "1" : "0");
                isExist = TRUE;
                break;
            }
        }

        if(! isExist)
        {
            chprintf(stream, "Unknown IO name!\n");
        }
    }

    return 0;
}

SHELL_CMD(in, _ShellCmdIoIn, "Shows input pin level", "[<pin name>]");

static int _ShellCmdIoOut(void *stream, int argc, char *argv[])
{
    HW_Out_t out;
    unsigned int len, i;
    bool isExist;
    int l;

    if(argc == 1)
    {
        for(out = 0; out < ARRAY_LEN(_outs); out++)
        {
            chprintf(stream, "%s: %s\n", _outs[out].name,
                    HW_GetSetted(out) == HW_Level_High ? "1" : "0");
        }
    }
    else if(argc >= 2)
    {
    	len = strlen(argv[1]);
        for(out = 0; out < ARRAY_LEN(_outs); out++)
        {
            if(len != strlen(_outs[out].name))
            {
        		isExist = FALSE;
            	continue;
            }

            isExist = TRUE;
            for(i = 0; i < len; i++)
            {
            	if(toupper(argv[1][i]) != toupper(_outs[out].name[i]))
            	{
            		isExist = FALSE;
            		break;
            	}
            }

            if(isExist)
            {
                isExist = TRUE;
                break;
            }
        }

        if(isExist)
        {
            if(argc == 2)
            {
                chprintf(stream, "%s: %s\n", _outs[out].name,
                        HW_GetSetted(out) == HW_Level_High ? "1" : "0");
            }
            else
            {
                l = atoi(argv[2]);
                HW_Set(out, l ? HW_Level_High : HW_Level_Low);
                chprintf(stream, "%s: Set to %s\n", _outs[out].name, l ? "1" : "0");
            }
        }
        else
        {
            chprintf(stream, "Unknown IO name!\n");
        }
    }

    return 0;
}

SHELL_CMD(out, _ShellCmdIoOut, "Shows or set output pin level", "[<pin name> [0|1]]");
